package com.jiming.webrpc.session;

import java.util.List;
import java.util.UUID;

import com.jiming.webrpc.Method;
import com.jiming.webrpc.Msg;
import com.jiming.webrpc.MsgBody;
import com.jiming.webrpc.MsgLev;
import com.jiming.webrpc.RemoteHost;
import com.jiming.webrpc.Result;
import com.jiming.webrpc.RpcResult;
import com.jiming.webrpc.RpcResultStatus;
import com.jiming.webrpc.SessionStatus;
import com.jiming.webrpc.utiles.ThreadUtile;


/**
 * 和远程主机建立的一次会话
 * @author straw
 */
public class Session {
	private String id;
	
	private int errorCount;//记录发送失败次数,超过一定次数需要重新发起会话或者对方不在在线时，关闭session
	
	private SessionStatus status = SessionStatus.closed;
	
	private RemoteHost remoteHost = null;
	
	private Thread do_handler_work = null;
	
	private Thread heartbeat_worker = null;
	
	private Session() {}
	
	public boolean isOpen() {
		return SessionStatus.started == status;
	}
	
	
	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public RemoteHost getRemoteHost() {
		return remoteHost;
	}

	public void setRemoteHost(RemoteHost remoteHost) {
		this.remoteHost = remoteHost;
	}

	public static Session newBuile(RemoteHost remoteHost) {
		Session session = new Session();
		session.setId(UUID.randomUUID().toString());
		session.setRemoteHost(remoteHost);
		
		//开启接受request msg的工作
		session.startDoRequestMsg();
		
		return session;
	}

	public static Session newBuile(RemoteHost remoteHost, String sessionId) {
		Session session = new Session();
		session.setId(sessionId);
		session.setRemoteHost(remoteHost);
		
		//开启接受request msg的工作
		session.startDoRequestMsg();
		
		return session;
	}
	
	private void startDoRequestMsg() {
		if(do_handler_work != null) {
			return;
		}
		
		do_handler_work = new Thread(() -> {
			while(true) {
				try {
					List<Msg> requests = this.getRemoteHost().getLocalHost().takeSessionAllRequestMsg(this.getId());
					for(Msg requestMsg:requests) {
						System.out.println("session收到:" + requestMsg.toString());
						if(Method.heartbeat == requestMsg.getBody().getMethod()) {
							//心跳包久忽略
							continue;
						}
						
						//交给SessionRequestHandler处理，如果有返回msg就响应业务msg，否则就响应一个收到的确认
						String data = this.getRemoteHost().getLocalHost().getSessionRequestHandler().request(requestMsg.getBody());
						if(data == null || "".equals(data)) {
							//收到确认响应(原路返回不需要回复)
							Msg responseMsg = Msg.newBuiled().setLev(MsgLev.session).setId(requestMsg.getId()).setSessionId(requestMsg.getSessionId()).setBody(MsgBody.newBuiled().setMethod(Method.response));
							this.remoteHost.getLocalHost().sendPackage(responseMsg, this.remoteHost.getNatIp(), this.remoteHost.getNatPort());
						}else {
							//业务响应(原路返回不需要回复)
							Msg returnMsg = Msg.newBuiled().setLev(MsgLev.session);
							returnMsg.setId(requestMsg.getId());
							returnMsg.setSessionId(requestMsg.getSessionId());
							returnMsg.setBody(MsgBody.newBuiled().setMethod(Method.response).setData(data));
							this.remoteHost.getLocalHost().sendPackage(returnMsg, this.remoteHost.getNatIp(), this.remoteHost.getNatPort());
						}
						
					}
					
				}catch (Exception e) {
				}
				
				ThreadUtile.sleep(1);
			}
		});
		do_handler_work.start();
	}

	/**
	 * 释放
	 */
	public synchronized void close() {
		System.out.println("关闭会话...");
		if(do_handler_work != null) {
			do_handler_work.interrupt();
			do_handler_work = null;
		}
		
		if(heartbeat_worker != null) {
			heartbeat_worker.interrupt();
			heartbeat_worker = null;
		}
		
		status = SessionStatus.closed;
	}

	/**
	 * 开始建立连接
	 */
	public synchronized void start() {
		System.out.println("申请与对方建立连接，ip=" + this.remoteHost.getNatIp() + ",port=" + this.remoteHost.getNatPort());
		/**
		 * 这里判断自己是否是Symmetric NatType？如果是那么自己需要先发送打洞udp，否则就是对方先打洞
		 */
		status = SessionStatus.starting;
		if(this.remoteHost.getLocalHost().isSymmetricNatType()) {
			//开始发送打洞数据包，要定时发送，不能断，否则长时间不发nat会销毁最初的打洞，链接会中断
			startHeartbeat();
		}
		
		//服务器转发告知也给我们发一个回礼包,等对方先打开session开始打洞我们才开始继续打洞
		String id2 = UUID.randomUUID().toString();
		String start_session_data = "token=" + this.remoteHost.getToken() + ",sessionId=" + this.getId();
		MsgBody startSBody = MsgBody.newBuiled().setMethod(Method.start_session).setData(start_session_data);
		Msg startSMsg = Msg.newBuiled().setId(id2).setSessionId(this.getId()).setLev(MsgLev.sys).setBody(startSBody).builed();
		RpcResult rpcResult = this.remoteHost.getLocalHost().sendPackageAndReturn(startSMsg, this.remoteHost.getLocalHost().getWebrpc_server_ip(), this.remoteHost.getLocalHost().getWebrpc_server_port());
		if(RpcResultStatus.succes == rpcResult.getStatus()) {
			//开始发送打洞数据包，要定时发送，不能断，否则长时间不发nat会销毁最初的打洞，链接会中断
			startHeartbeat();
			
			String data = rpcResult.getMsg().getBody().getData();
			String sessionStatusName = data.split(",")[0].split("=")[1];
			String sessionId = data.split(",")[1].split("=")[1];
			this.setId(sessionId);
			
			SessionStatus sessionStatus = SessionStatus.valueOf(sessionStatusName);
			if(SessionStatus.starting == sessionStatus) {
				System.out.println("发送1次hello");
				//尝试发送问候包，最多尝试3次，每个3秒发一次，如果没有回复则会话连接失败
				int i = 1;
				Msg helloMsg = newHelloMsg(i);
				RpcResult helloRpcResult = this.remoteHost.getLocalHost().sendPackageAndReturn(helloMsg, this.remoteHost.getNatIp(), this.remoteHost.getNatPort());
				if(RpcResultStatus.succes == helloRpcResult.getStatus()) {
					//收到回复，会话连接成功
					status = SessionStatus.started;
				}else {
					System.out.println("发送2次hello");
					//再发2次
					i = 2;
					helloMsg = newHelloMsg(i);
					helloRpcResult = this.remoteHost.getLocalHost().sendPackageAndReturn(helloMsg, this.remoteHost.getNatIp(), this.remoteHost.getNatPort());
					if(RpcResultStatus.succes == helloRpcResult.getStatus()) {
						//收到回复，会话连接成功
						status = SessionStatus.started;
						
					}else {
						System.out.println("发送3次hello");
						//再发3次
						i = 3;
						helloMsg = newHelloMsg(i);
						helloRpcResult = this.remoteHost.getLocalHost().sendPackageAndReturn(helloMsg, this.remoteHost.getNatIp(), this.remoteHost.getNatPort());
						if(RpcResultStatus.succes == helloRpcResult.getStatus()) {
							//收到回复，会话连接成功
							status = SessionStatus.started;
							
						}
					}
				}
				
				if(!this.isOpen()) {
					//打洞失败，无法与远程主机建立链接
					System.out.println("打洞失败，无法与远程主机建立链接...");
					this.close();
				}
			}else if(SessionStatus.closed == sessionStatus) {
				//远程主机已经关闭会话，链接失败
				System.out.println("远程主机已经关闭会话，链接失败...");
				this.close();
			}else if(SessionStatus.timeout == sessionStatus) {
				//远程主机超时无响应，链接失败
				System.out.println("远程主机超时无响应，链接失败...");
				this.close();
			}else if(SessionStatus.started == sessionStatus) {
				//远程主机已经和本机成功打开的会话
				System.out.println("远程主机已经和本机成功打开的会话...");
				status = SessionStatus.started;
			}
			
		}else {
			//申请失败，无法与对方建立会话连接
			System.out.println("申请失败，无法与对方建立会话连接,服务器无响应");
			this.close();
		}
		
		
	}
	
	/**
	 * 心跳
	 */
	private void startHeartbeat() {
		if(heartbeat_worker != null) {
			return;
		}
		
		heartbeat_worker = new Thread(() -> {
			while (true) {
				try {
					String id = UUID.randomUUID().toString();
					Msg sysMsg = Msg.newBuiled().setId(id).setLev(MsgLev.session).setSessionId(this.getId()).setBody(MsgBody.newBuiled().setMethod(Method.heartbeat)).builed();
					this.remoteHost.getLocalHost().sendPackage(sysMsg, this.remoteHost.getNatIp(),
							this.remoteHost.getNatPort());// 这里不会有回应

					// 3秒钟发送一次
					ThreadUtile.sleep(3 * 1000);
				} catch (Exception e) {
				}
			}

		});
		heartbeat_worker.start();
	}

	private Msg newHelloMsg(int i) {
		String id = UUID.randomUUID().toString();
		MsgBody helloBody = MsgBody.newBuiled().setMethod(Method.request).setData("hello_i=" + i);
		Msg helloMsg = Msg.newBuiled().setId(id).setLev(MsgLev.session).setBody(helloBody).setSessionId(this.getId()).builed();
		return helloMsg;
	}

	/**
	 * 同意与远程连接
	 */
	public synchronized void agreeConnection() {
		System.out.println("同意与远程连接");
		//开始发送打洞数据包，要定时发送，不能断，否则长时间不发nat会销毁最初的打洞，链接会中断
		status = SessionStatus.starting;
		startHeartbeat();
		
		// 尝试发送问候包，最多尝试3次，每个3秒发一次，如果没有回复则会话连接失败
		System.out.println("发送1次hello");
		int i = 1;
		Msg helloMsg = newHelloMsg(i);
		RpcResult helloRpcResult = this.remoteHost.getLocalHost().sendPackageAndReturn(helloMsg, this.remoteHost.getNatIp(),
				this.remoteHost.getNatPort());
		if (RpcResultStatus.succes == helloRpcResult.getStatus()) {
			// 收到回复，会话连接成功
			status = SessionStatus.started;
			
		} else {
			// 再发2次
			System.out.println("发送2次hello");
			i = 2;
			helloMsg = newHelloMsg(i);
			helloRpcResult = this.remoteHost.getLocalHost().sendPackageAndReturn(helloMsg, this.remoteHost.getNatIp(),
					this.remoteHost.getNatPort());
			if (RpcResultStatus.succes == helloRpcResult.getStatus()) {
				// 收到回复，会话连接成功
				status = SessionStatus.started;

			} else {
				// 再发3次
				System.out.println("发送3次hello");
				i = 3;
				helloMsg = newHelloMsg(i);
				helloRpcResult = this.remoteHost.getLocalHost().sendPackageAndReturn(helloMsg, this.remoteHost.getNatIp(),
						this.remoteHost.getNatPort());
				if (RpcResultStatus.succes == helloRpcResult.getStatus()) {
					// 收到回复，会话连接成功
					status = SessionStatus.started;

				}
			}
		}
		
		if(!this.isOpen()) {
			//打洞失败，无法与远程主机建立链接
			System.out.println("打洞失败，无法与远程主机建立链接...");
			this.close();
		}
	}


	public SessionStatus getStatus() {
		// TODO Auto-generated method stub
		return this.status;
	}

	public boolean isStarting() {
		// TODO Auto-generated method stub
		return SessionStatus.starting == this.status;
	}

	/**
	 * 向会话远程发送数据,返回成功或者失败
	 * @param string
	 * @return
	 */
	public Result send(String data) {
		if(this.isOpen()) {
			String id = UUID.randomUUID().toString();
			MsgBody helloBody = MsgBody.newBuiled().setMethod(Method.request).setData(data);
			Msg msg = Msg.newBuiled().setId(id).setLev(MsgLev.session).setBody(helloBody).setSessionId(this.getId()).builed();
			RpcResult rpcResult = this.remoteHost.getLocalHost().sendPackageAndReturn(msg, this.remoteHost.getNatIp(), this.remoteHost.getNatPort());
			if(RpcResultStatus.succes == rpcResult.getStatus()) {
				errorCount = 0;
				return Result.newBuild().setStatus(Result.Status.success).setData(rpcResult.getMsg().getBody().getData());
			}else {
				errorCount++;
				if(errorCount > 3) {
					//超过3次失败,判断对方是否还在线？
					if(this.remoteHost.isOnline()) {
						this.reStart();
						errorCount = 0;
					}else {
						//对方不在线了，关闭session
						this.close();
					}
				}
				return Result.newBuild().setStatus(Result.Status.fialed);
			}
		}else {
			System.out.println("会话没有开启");
			if(this.remoteHost.isOnline()) {
				System.out.println("远程主机重新上线，继续重连...");
				this.reStart();
				errorCount = 0;
			}
			return Result.newBuild().setStatus(Result.Status.remotediscon);
		}
	}

	/**
	 * 重新打开会话
	 */
	private synchronized void reStart() {
		System.out.println("会话重启!");
		this.close();
		this.start();
	}

	public boolean isClosed() {
		return SessionStatus.closed == status;
	}


	
}
